En el presente estudio se procederá a analizar el data set llamado “Netflix Prize data” obtenido en la página web Kaggle. Dicho dataset se creó como una competición para encontrar el mejor algoritmo de predicción de la puntuación que los usuarios dan a las películas de la plataforma.
Antes de extraer la información y comenzar a trabajar sobre ella se ha procedido a visualizar a qué tipo de datos íbamos a enfrentarnos. El dataset contiene diversos archivos pero solo nos centraremos con los que vamos a trabajar en este estudio:
Los archivos ‘combined_data_{nº}’ contienen la información de las puntuaciones de los usuarios para determinado código de película, así como el identificador de usuario y la fecha (día, mes y año) en la que se dio dicha valoración.
Esta base de datos cuenta con un tamaño de espacio en memoria demasiado elevado, por lo que se procederá a extraer los datos de 250 películas escogidas de manera aleatoria con una semilla (“2495”).
Otro de los archivos que se utilizarán en este estudio contiene el título de cada ID asociado a las películas además del año en el que se estrenó. En secciones posteriores explicaremos como realizaremos la inclusión de estos datos junto con los datos iniciales para obtener el dataset final con el que trabajaremos de forma más cómoda.
Una vez realizado este proceso generaremos una nueva variable para incluirla en nuestro estudio ya que sería interesante medir la diferencia de años transcurridos desde el estreno hasta la puntuación de las diferentes películas (“YearsSinceRelease”).
El primer paso para realizar la carga de datos es leer el fichero “filas_ID_combined_all.txt” de donde obtendremos por cada ID de película el número del fichero donde se encuentran las puntuaciones de cada usuario y las posiciones de la fila inicial y final de estas. De la misma forma, cargaremos los cuatro ficheros ‘combined_data_{nº}’ con la información de las puntuaciones de las películas.
Ya que el fichero “filas_ID_combined_all.txt” cuenta con la información de todas las películas, después de exportarlo realizaremos un filtrado para quedarnos solo con las filas de las 250 películas que nos interesan estudiar. Seguidamente, utilizaremos la función obtain_movies para que, pasados los parámetros del fichero “filas_ID_combined_all.txt” y el ID de ‘combined_data_{nº}’, calculemos todas las posiciones a leer y nos devuelva un combined_data con solo las observaciones de las películas que sabemos que se encuentran en dicho fichero.
#Función que obtiene los datos de filas específicas
obtain_movies = function(idx, combined_data, index_data){
#Obtenemos las películas del archivo a leer (1,2,3 o 4)
rows_data = filter(index_data, data == idx)
rows_data = select(rows_data, fila, fila_final)
#Por cada fila guardamos el rango de posiciones a leer en el fichero
range = apply(rows_data, 1, function(i){c(i[1]:i[2])})
range = unlist(range)
#Nos quedamos solo con los datos de las películas de la muestra
return (slice(combined_data[[idx]], range))
}#Obtenemos nuestra muestra de películas
n_muestra = 250
set.seed(2495)
muestra_grupo = sample(1:17770, n_muestra, replace = FALSE)
#Leemos el fichero y nos quedamos con el índice de las películas de nuestra muestra
filas_ID_combined_all = read.csv("data/filas_ID_combined_all.txt")
filas_ID_combined_all %<>%
filter(filas_ID_combined_all$ID %in% muestra_grupo) %>%
select(ID, fila, fila_final, data)
#Cargamos los ficheros con la información de Netflix
file_names = list.files("data/", pattern = "^combined_data")
file_list = lapply(file_names, function(i){read_tsv(file.path("data", i), col_names = FALSE)})
#Leemos las observaciones de las películas de nuestra muestra por cada fichero
data_list = lapply(seq_along(file_list), FUN = obtain_movies, combined_data = file_list, index_data = filas_ID_combined_all)
#Juntamos los datos de las películas de los 4 ficheros
df_ratings = bind_rows(data_list)
#Exportamos los datos de las películas de nuestra muestra
write.table(df_ratings,"data/data_sample.txt", row.names = FALSE, col.names = FALSE)
#Eliminamos variables que ya no son útiles
rm(data_list, file_list, file_names, filas_ID_combined_all)Una vez hemos exportado los ficheros necesarios y filtrado las observaciones de interés, procedemos a la limpieza de datos y construcción del modelo de datos final. Generamos un dataframe que por cada observación contenga: ID de la película, ID de usuario, calificación y fecha de calificación.
#Se asigna una posición a cada observación para posteriormente indicar el id de película de cada una de ellas
df_ratings = df_ratings %>%
mutate(Idx = row_number())
#Guardamos la fila donde empieza cada película
movie_rows = grep(":", df_ratings$X1)
#Añadimos el id de la película a cada posición y eliminamos el caracter ":"
rows_ID = df_ratings %>%
filter(Idx %in% movie_rows) %>%
mutate(X1 = as.integer(gsub(":","",X1)))
#Número de veces que se tendrá que repetir el identificador de cada película
reps = diff(c(rows_ID$Idx, max(df_ratings$Idx) + 1))
netflix = df_ratings %>%
mutate(MovieID = rep(rows_ID$X1, times = reps)) %>%
filter(!(Idx %in% rows_ID))
#Se definen las columnas del dataframe
netflix %<>%
separate(X1,into = c("UserID","Rating","RatingDate"), sep = ",") %>%
na.omit(netflix) %>%
mutate(Idx = row_number())
#Se eliminan las variables auxiliares
rm(df_ratings, movie_rows, rows_ID, reps)Con el objetivo de completar nuestro dataset, realizamos la unión de la tibble netflix con los datos exportados del fichero movies_titles.csv, añadiendo así la variable del nombre de la película y el año de estreno. Una vez se ha realizado el inner join, se procede al cambio de tipo de cada variable para que concuerde con la información que representan.
#Lectura del fichero movie_titles.csv
df_movies = read_tsv("data/movie_titles.csv", locale = readr::locale(encoding = "ISO-8859-1"), col_names=FALSE)
df_movies %<>%
separate(X1,",",into =c("MovieID","MovieRelease","Title"), extra="merge") %>%
filter(MovieID %in% muestra_grupo)
#Se transforma la variable MovieRelease a numérica
df_movies$MovieRelease = as.numeric(df_movies$MovieRelease)
#Se transforma la variable MovieRelease a numérica
netflix$MovieID = as.character(netflix$MovieID)
#Se unifica el dataframe de las puntuaciones con el de las películas
netflix = inner_join(x = netflix, y = df_movies, by = "MovieID", all = TRUE)
#Se transforma la variable 'Rating' a tipo númerica
netflix$Rating = as.numeric(netflix$Rating)
#Se transforma la variable 'RatingDate' a tipo date
netflix$RatingDate = as.Date(netflix$RatingDate, format = "%Y-%m-%d")
#Se añade la diferencia en años entre el año de puntuación y el de estreno de la película
netflix = mutate(netflix, YearsSinceRelease = year(RatingDate) - MovieRelease)
#Se transforma la variable YearsSinceRelease a numérica
netflix$YearsSinceRelease = as.numeric(netflix$YearsSinceRelease)
#Adicionalmente, dividimos la variable RatingDate en: día, mes y año
#Se ordenan las posiciones de las columnas y se indican su nuevo nombre
netflix %<>%
separate(RatingDate,into = c("Year","Month","Day"), sep = "-", remove = FALSE) %>%
na.omit(netflix) %>%
mutate(Idx = row_number()) %>%
select(Idx, MovieID, Title, UserID, Rating, Day, Month, Year, RatingDate, MovieRelease, YearsSinceRelease)
#Exportamos el fichero preparado y construido para empezar a trabajar en su análisis
write.csv(netflix, "data/netflix.csv", row.names = FALSE)Una vez tenemos la tibble final sobre la que vamos a trabajar, procedemos a estudiar su estructura:
## tibble [1,093,856 x 11] (S3: tbl_df/tbl/data.frame)
## $ Idx : int [1:1093856] 1 2 3 4 5 6 7 8 9 10 ...
## $ MovieID : chr [1:1093856] "43" "43" "43" "43" ...
## $ Title : chr [1:1093856] "Silent Service" "Silent Service" "Silent Service" "Silent Service" ...
## $ UserID : chr [1:1093856] "305151" "497196" "2327803" "2625420" ...
## $ Rating : num [1:1093856] 4 3 1 2 5 2 1 3 5 2 ...
## $ Day : chr [1:1093856] "20" "13" "19" "13" ...
## $ Month : chr [1:1093856] "02" "04" "08" "05" ...
## $ Year : chr [1:1093856] "2005" "2003" "2001" "2004" ...
## $ RatingDate : Date[1:1093856], format: "2005-02-20" "2003-04-13" ...
## $ MovieRelease : num [1:1093856] 2000 2000 2000 2000 2000 2000 2000 2000 2000 2000 ...
## $ YearsSinceRelease: num [1:1093856] 5 3 1 4 3 3 5 2 3 3 ...
Idx: Variable numérica que determina la posición o índice de cada observación del dataset.
MovieID: Variable [TODO] que identifica a cada película con un ID determinado, este ID está asociado a un título que será representado por la variable “Title”.
Title: Variable con datos definidos como caracteres. Esta variable representa el título de cada una de las películas identificadas con un ID en la variable “MovieID”.
UserID: Variable con datos definidos como caracteres que representa el ID único del usuario que ha calificado la película.
Rating: Variable numérica ordinal que representa la calificación que ha hecho cada usuario de las películas que ha puntuado. Esta calificación puede ser dentro del rango [1,2,3,4,5], donde 1 estrella representa la calificación más baja y 5 estrellas la calificación más alta.
RatingDate: Variable definida como tipo **date*, que representa la fecha en la cual el usuario realizó la calificación de una determinada película.
MovieRelease: Variable numérica que indica el año en el cual se estrenó la película. El año de estreno puede referirse tanto a estreno de una determinada película en el cine como en DVD.
YearsSinceRelease: Variable numérica la cual se ha calculado mediante la resta de RatingDate y MovieRelease. Esta variable indica el número de años que ha pasado desde que se ha estrenado una determinada película hasta que el usuario la ha calificado.
ggplot(data = netflix, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "lightcoral", high = "firebrick2")+
labs(x = "Rating", y = "Number of ratings", title = "Total Ratings by stars")+
scale_y_continuous(limits=c(0,380000), labels = scales::comma)+
coord_flip() +
theme_classic()
# Image in the visualization
image = image_read("imgs/icon-rating.png")
grid.raster(image, x = 0.80, y = 0.25, height = 0.23)Número total de calificaciones por estrella.
Como se puede observar en el gráfico, las puntuaciones generalmente han sido positivas ya que el mayor número de concentración de observaciones se encuentra entre 3 estrellas o más. De hecho, si realizamos la medía de todas las calificaciones vemos que es de 3.5603827 estrellas.
La calificación de 4 estrellas es la moda con un total de 368895 votaciones.
bar_plot2 = ggplot(data = netflix) +
geom_bar(aes(x = Month, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
show.legend = FALSE,
color = c("darkslategray3", "darkslategray3", "darkslategray3", "lightpink2",
"lightpink2", "lightpink2", "lightgoldenrod2", "lightgoldenrod2", "lightgoldenrod2",
"tan2", "tan2", "tan2"),
fill = c("darkslategray2", "darkslategray2", "darkslategray2", "lightpink1",
"lightpink1", "lightpink1", "lightgoldenrod1", "lightgoldenrod1", "lightgoldenrod1",
"tan1", "tan1", "tan1"),
size = 1) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.15)) +
scale_x_discrete(labels = month.abb) +
ylab("% Ratings") +
xlab("Rating month") +
ggtitle("Percentage of ratings by month") +
theme_minimal()
div(ggplotly(bar_plot2), align ="center")El mes de enero es el mes en el que se realizaron el menor número de calificaciones (6.8% del total).
#Añadimos una variable nueva en el dataset que indique el día de la semana según la fecha de la calificación
netflix$DayOfWeek = weekdays(netflix$RatingDate)
bar_plot3 = ggplot(data = netflix) +
geom_bar(aes(x = DayOfWeek, y = ..count../sum(..count..), fill = ..count../sum(..count..)),
show.legend = FALSE,
color = c("indianred4", "lightgoldenrod2", "palevioletred2", "palegreen4",
"tan2", "steelblue3", "mediumorchid4"),
fill = c("indianred3", "lightgoldenrod1", "palevioletred1", "palegreen3",
"tan1", "steelblue2", "mediumorchid3"),
size = 1) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.25)) +
scale_x_discrete(limits = c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")) +
ylab("% Ratings") +
xlab("Rating day of week") +
ggtitle("Percentage of ratings by day of week") +
theme_minimal()
div(ggplotly(bar_plot3), align ="center")El número de votaciones según el día de la semana sigue una distribución asimétrica positiva. Es decir, al principio se califican más películas y, a medida que se acerca el fin de semana, se reduce el número de votaciones.
El martes es el día de la semana en el cual se han registrado más calificaciones con un 17.77% del total, mientras que el sábado es el día en el cual se han calificado menos películas con un 9.82% del total.
Como se observa, de lunes a jueves se encuentra más del 60% de las calificaciones y, el viernes y fin de semana son los días los cuales se puntúan un menor número de películas.
movies_title = sort(table(netflix$Title), decreasing = TRUE)
movies_title = as.data.frame(movies_title) %>%
rename(Title = Var1)
set.seed(2)
ggplot(movies_title, aes(label = Title,
size = Freq,
color = Freq,
angle = sample(c(0,15,30, 45,60, 75,90, 105,120,135, 160), 250, replace = TRUE))) +
geom_text_wordcloud_area(mask = png::readPNG("imgs/netflix-mask2.png"),
rm_outside = T) +
scale_color_gradient(low = "indianred1", high = "red2") +
theme_minimal()Películas más votadas.
set.seed(2495)
wordcloud2(movies_title, size = 0.39)Películas más votadas.
[TODO]
import pandas as pd
netflix_py = pd.read_csv("data/netflix.csv")
res = pd.concat([netflix_py.groupby('MovieID')['Rating'].describe(),
netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.mode).rename('mode'),
netflix_py.groupby('MovieID')['Rating'].agg(pd.Series.median).rename('median')
], axis=1).T
print(res)## MovieID 43 195 ... 17606 17751
## count 105.000000 185.000000 ... 87.000000 2475.000000
## mean 2.571429 3.297297 ... 2.229885 3.938182
## std 1.284951 1.039044 ... 1.075100 1.133302
## min 1.000000 1.000000 ... 1.000000 1.000000
## 25% 1.000000 3.000000 ... 1.000000 3.000000
## 50% 3.000000 3.000000 ... 2.000000 4.000000
## 75% 3.000000 4.000000 ... 3.000000 5.000000
## max 5.000000 5.000000 ... 5.000000 5.000000
## mode 3.000000 3.000000 ... 1.000000 5.000000
## median 3.000000 3.000000 ... 2.000000 4.000000
##
## [10 rows x 250 columns]
#Tabla agrupada por película y año la valoración.
movies_year_rating = table(netflix$Title, netflix$Year)
movies_year_rating = as.data.frame(movies_year_rating)
movies_year_rating %<>%
rename(MovieTitle = Var1, Year = Var2) %>%
filter(MovieTitle %in% movies_title[1:10,1])
#Representación gráfica.
line_plot = ggplot(movies_year_rating,
aes(x = Year, y = Freq,
group = MovieTitle,
color = MovieTitle,
shape = MovieTitle)) +
scale_shape_manual(values=1:10) +
geom_point() +
geom_line() +
ylab("Ratings number") +
xlab("Rating's year") +
theme_minimal()
div(ggplotly(line_plot), align ="center")## Warning: `group_by_()` is deprecated as of dplyr 0.7.0.
## Please use `group_by()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.
Las 10 películas más votadas fueron: Bad Boys, Final Destinatión, Freaky Friday, Good morning Vietnam, Lethal Weapon 3, Meet Joe Black, Monster-in-Law, Some Like It Hot, The Grudge y The Rundown.
Como podemos observar, las 10 películas más calificadas fueron las puntuadas a partir de 1999, recibiendo calificaciones este mismo año 3 películas (Good Morning vietnam, Lethal Weapon 3 y Meet Joe Black). Al año siguiente empezaron a calificar 2 películas más a parte de las anteriores (Bad Boys y Final Destination), en 2001 se sumo la película Some Like It Hot, y un año después las películas The Rundown y Freaky Friday. Seguidamente en 2003 comenzó a recibir calificaciones la película The Grudge y, finalmente, en 2004 empezó a ser calificada la película Monster-in-Law.
Esta cantidad de calificaciones fue aumentando cada año hasta situarse en el 2005 con el año en el cual cada película recibió más calificaciones con excepción de 2 casos: Freaky Friday y te Rundown quienes recibieron la mayor cantidad en el año 2004 con un total de 43.612 y 25.056 votos respectivamente.
El reto de películas, como se ha comentado recibió el mayor número de calificaciones en 2005, siendo estas cantidades:
data = filter(netflix, Title %in% movies_title[1:10,1]) %>%
group_by(Title, Year) %>%
summarise(mean = mean(Rating))
bar_plot_facet = ggplot(data, aes(fill=Title, y=mean, x=Year)) +
geom_bar(position="dodge", stat="identity") +
facet_wrap(~Title, scales='free') +
scale_x_discrete(limits=c("1999", "2000", "2001", "2002", "2003", "2004", "2005")) +
scale_y_discrete(limits=c(0:5)) +
theme(panel.spacing.x=unit(1, "lines") , panel.spacing.y=unit(3,"lines"),
axis.text.x = element_text(angle = 45))
div(ggplotly(bar_plot_facet), align ="center")data_boxplot = filter(netflix, Title %in% movies_title[1:5, 1])
boxplot = ggplot(data_boxplot, aes(Title, Rating, group=Title)) +
geom_boxplot(
fill = c("lightsalmon1", "palegreen2", "tomato2", "thistle3", "paleturquoise2" ),
colour = c("lightsalmon4", "palegreen4","tomato4", "thistle4", "paleturquoise4" ),
outlier.colour = c("lightsalmon4", "palegreen4","tomato4", "thistle4", "paleturquoise4" )) +
geom_point(stat= "summary", shape=20, size=2, color="gray28") +
theme(axis.text.x = element_text(angle = 45))
div(ggplotly(boxplot), align ="center")#Más votadas
data_morevoted = filter(netflix, Title == movies_title[1, 1])
ggplot(data_morevoted, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "cadetblue1", high = "cadetblue4")+
labs(x = "Rating", y = "Number of ratings", title = data_morevoted$Title[1]) +
scale_y_continuous(limits=c(0,45000)) +
coord_flip() +
theme_classic()
#Image in the visualization
image = image_read("imgs/masvotada.jpg")
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)Calificaciones de la película más votada.
La película que ha obtenido el mayor número de calificaciones es Good morning, Vietnam, una película dramática estadounidense estrenada en 1987.
#Menos votada
data_lessvoted = filter(netflix, Title == movies_title[nrow(movies_title), 1])
ggplot(data_lessvoted, aes(x = Rating)) +
geom_bar(aes(y = ..count.., fill = ..count..),
stat="count",
show.legend = FALSE) +
geom_label(aes(label = ..count.., y = ..count..),
stat = "count",
vjust = -.5) +
scale_fill_gradient(low = "darkgoldenrod1", high = "darkgoldenrod3") +
labs(x = "Rating", y = "Number of ratings", title = data_lessvoted$Title[1]) +
scale_y_continuous(limits=c(0,8)) +
coord_flip() +
theme_classic()
# Image in the visualization
image = image_read("imgs/menosvotada.jpg")
grid.raster(image, x = 0.80, y = 0.35, height = 0.4)Calificaciones de la película menos votada.
Para estudiar la distribución del número de películas que se estrenaron cada año, realizamos un histograma que muestre por cada década desde el 1920 al 2010 el porcentaje total de producciones realizadas para ese periodo de años.
break_range = seq(from = 1920, to = 2010, by = 10)
hist_plot1 = ggplot(data = df_movies) +
geom_histogram(aes(x = MovieRelease, y = ..count../sum(..count..)),
breaks = break_range,
color = c("darkgoldenrod2", "lightgoldenrod1", "lightblue", "palegreen3",
"lightpink2", "plum3", "steelblue3", "darkolivegreen3", "salmon3"),
fill = c("darkgoldenrod1", "khaki1", "lightblue", "palegreen", "lightpink1",
"plum2", "steelblue1", "darkolivegreen2", "salmon1"),
size = 1) +
geom_density(aes(x = MovieRelease, y = 10*..density..),
color = "black",
fill = "seashell3",
size = 1,
alpha = 0.2,
show.legend = FALSE) +
scale_x_continuous(breaks = break_range) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1),
limits = c(0, 0.5)) +
scale_color_gradient(low = "indianred1", high = "red2") +
ylab("% of movies") +
xlab("Movie release's decade") +
ggtitle("Percentage of movies released by decade") +
theme_minimal()
ggplotly(hist_plot1)Porcentaje películas según la década de estreno.
Como podemos observar en el gráfico, el número de películas estrenada según la década sigue una distribución asimétrica positiva.
En netflix se valoraron solo 1.7% de películas estrenadas en la década 1920. Dicho porcentaje ha ido aumentando en las posteriores décadas hasta los 90 donde se alcanza el punto prominente con un 38% del total de calificaciones registradas en el presente estudio. A principios de los 2000 el número de votaciones ha descendido aproximadamente un 10% respecto a la década anterior, pero esto se debe al muestreo de películas que estamos analizando.
En general, se puede concluir que cuanto más moderna es la película más votaciones tiene, puesto que la gente prefiere visualizar producciones más actuales que antiguas. Esto también puede darse por el catálogo de Netflix, que dispone de películas más actuales.
netflix %>%
count(YearsSinceRelease, Rating) %>%
ggplot(mapping = aes(x = Rating, y = YearsSinceRelease)) +
geom_tile(mapping = aes(fill = n, width = 0.7, height = 0.7),
size = 2) +
scale_fill_gradientn(colours = c("lightgoldenrod1", "coral", "orange", "purple", "maroon")) +
theme_minimal()Calificaciones según la diferencia entre años de estreno y de votación.